package com.xiaoxin.experience.system.properties;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.LoggerContext;
import com.xiaoxin.experience.util.StringUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;

import javax.annotation.Resource;
import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;

/**
 * @author xiaoxin
 */
@Component
public class BeanValueUtil {

    @Resource
    ApplicationContext applicationContext;

    private static final ConversionService conversionService = new DefaultConversionService();

    private static final Logger log = LoggerFactory.getLogger(BeanValueUtil.class);

    private BeanValueUtil() {}

    public void handleLoggerLevel(Properties properties)
    {
        if (null == properties)
        {
            return;
        }

        String logPrefix = "logging.level.";

        try
        {
            LoggerContext loggerContext = (LoggerContext)LoggerFactory.getILoggerFactory();

            properties.forEach((keyObj, valueObj) -> {
                String key = String.valueOf(keyObj);
                if (key.startsWith(logPrefix))
                {
                    String value = String.valueOf(valueObj);
                    String logPackage = key.substring(logPrefix.length());
                    loggerContext.getLogger(logPackage).setLevel(Level.valueOf(value));
                }
            });
        }
        catch (Exception e)
        {
            log.error("handle logger level fail.", e);
        }
    }

    private void handleProperties(Properties properties)
    {
        if (null == properties)
        {
            return;
        }

        try
        {
            Map<String, Object> propertyBeans = applicationContext.getBeansWithAnnotation(ConfigurationProperties.class);
            if (CollectionUtils.isEmpty(propertyBeans))
            {
                log.warn("no configuration properties found.");
                return;
            }

            propertyBeans.forEach((name, bean) -> {
                // 更新配置类bean对象值,根据类上注解@ConfigurationProperties进行搜索
                ConfigurationProperties cpAnn = applicationContext.findAnnotationOnBean(name, ConfigurationProperties.class);
                if (null != cpAnn)
                {
                    String prefix = StringUtils.isEmpty(cpAnn.prefix()) ? cpAnn.value() : cpAnn.prefix();
                    updatePropertiesToBean(prefix, bean, properties);
                }
            });

            log.debug("handle properties from remote properties service Finish.");
        }
        catch (Exception e)
        {
            log.error("handle properties fail.", e);
        }
    }

    private void updatePropertiesToBean(String prefix, Object bean, Properties properties)
    {
        final boolean noPrefix = StringUtils.isBlank(prefix);
        properties.forEach((key, value) -> {
            log.debug("handle properties with key[{}] and prefix[{}]", key, prefix);
            String propName = String.valueOf(key);
            propName = StringUtil.hyphenToCamel(propName);
            if (noPrefix)
            {
                changeFieldValue(bean, propName, value);
            }
            else if (propName.startsWith(prefix))
            {
                changeFieldValue(bean, propName.substring(prefix.length() + 1), value);
            }
        });
    }

    /**
     * 更新实体bean中的字段值，自动将传入模糊类型的新值转换为目标类型
     * <p>
     * 例如：target为{"id":11, "name":"aa", "mark":"xx"},fieldName为"id",fieldValue为"234"(String类型)
     * 则更新target为{"id":11, "name":"aa", "mark":"xx"}
     *
     * @param bean       需更新的实体bean
     * @param fieldName  字段名称
     * @param fieldValue 模糊类型的字段值
     */
    public static void changeFieldValue(Object bean, String fieldName, Object fieldValue)
    {
        log.debug("change field value with name[{}] and value[{}]", fieldName, fieldValue);
        try
        {
            PropertyDescriptor pd = BeanUtils.getPropertyDescriptor(bean.getClass(), fieldName);
            if (null != pd)
            {
                Class<?> target = pd.getPropertyType();
                if (target.isPrimitive())
                {
                    Object array = Array.newInstance(target, 1);
                    target = Array.get(array, 0).getClass();
                }
                fieldValue = conversionService.convert(fieldValue, target);

                Method writeMethod = pd.getWriteMethod();
                writeMethod.invoke(bean, fieldValue);
                log.debug("change field[{}] value[{}] finish.", fieldName, fieldValue);
            }
            else
            {
                int dotIndex = fieldName.indexOf('.');
                if (dotIndex > 0)
                {
                    log.debug("change field in sub class instance.");
                    String cClassName = fieldName.substring(0, dotIndex);
                    if (Modifier.isPublic(bean.getClass().getModifiers()))
                    {
                        PropertyDescriptor cpd = BeanUtils.getPropertyDescriptor(bean.getClass(), cClassName);
                        if (null != cpd)
                        {
                            Method readMethod = cpd.getReadMethod();
                            Object subClass = readMethod.invoke(bean);
                            changeFieldValue(subClass, fieldName.substring(dotIndex + 1), fieldValue);
                        }
                        else
                        {
                            log.debug("no sub class property descriptor found, no field changed.");
                        }
                    }
                    else
                    {
                        log.debug("bean class cannot be access for non-public modifiers.");
                    }
                }
                else
                {
                    log.debug("no field changed.");
                }
            }
        }
        catch (Exception e)
        {
            log.error("change field value fail.", e);
        }
    }

    /**
     * 刷新ioc容器中bean对象
     * 先刷新配置类属性,再刷新包含有@value注解的字段值内容
     * @param properties 要更新的@value值对应的键值对对象
     */
    public void refreshValue(Properties properties)
    {
        if (Objects.isNull(applicationContext) || Objects.isNull(properties))
        {
            log.error("refresh bean value fail! applicationContext/properties is null");
            return;
        }
        handleProperties(properties);
        // 更新使用@value注解注入的字段值,接口代理对象无法更新
        for (String beanName : applicationContext.getBeanDefinitionNames())
        {
            Object bean = applicationContext.getBean(beanName);
            Class<?> targetClass = AopUtils.getTargetClass(bean);
            Object singletonTarget = AopProxyUtils.getSingletonTarget(bean);
            if (Objects.nonNull(singletonTarget))
            {
                bean = singletonTarget;
            }

            Field[] declaredFields = targetClass.getDeclaredFields();
            for (Field f : declaredFields)
            {
                Value valueAnnotation = f.getAnnotation(Value.class);
                if (valueAnnotation == null)
                {
                    continue;
                }
                String key = valueAnnotation.value().replace("${", "").replace("}", "").split(":")[0];
                if (properties.containsKey(key))
                {
                    ReflectionUtils.makeAccessible(f);
                    try
                    {
                        filedSet(f,bean, properties.get(key));
                    }
                    catch (Exception e)
                    {
                        log.error(e.getMessage(), e);
                    }
                }
            }
        }
    }

    private static void filedSet(Field field, Object bean, Object value)
            throws IllegalAccessException
    {
        Class<?> type = field.getType();
        if(type.isAssignableFrom(String.class))
        {
            field.set(bean,value.toString());
        }
        else if (type.isAssignableFrom(Boolean.class) || "boolean".equals(type.getName()))
        {
            field.set(bean,Boolean.parseBoolean(value.toString()));
        }
        else if (type.isAssignableFrom(Integer.class) || "int".equals(type.getName()))
        {
            field.set(bean,Integer.parseInt(value.toString()));
        }
        else if (type.isAssignableFrom(Long.class) || "long".equals(type.getName()))
        {
            field.set(bean,Long.parseLong(value.toString()));
        }
        else if (type.isAssignableFrom(Double.class) || "double".equals(type.getName()))
        {
            field.set(bean,Double.parseDouble(value.toString()));
        }
        else
        {
            field.set(bean,value);
        }
    }
}
